﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Myotragus.Data.Domain;
using Myotragus.Data.Metadata;
using Myotragus.Data.Util;

namespace Myotragus.Data.Emit
{
	public abstract class LocalizableOperationEntityGenerator : EntityGenerator,
		ILocalizableOperationEntityGenerator
	{
		protected static readonly Type TypeOfIEntityGeneric = typeof(IEntity<>);

		protected LocalizableOperationEntityGenerator(IDynamicAssembly dynamicAssembly,
			INameGenerator nameGenerator, ILocalizableEntityMapping mapping)
			: base(dynamicAssembly, nameGenerator)
		{
			Mapping = mapping;
		}

		protected ILocalizableEntityMapping Mapping { get; private set; }

		protected override void AttachImplementation(IDynamicType type, Type interfaceType)
		{
			AttachNonLocalizableImplementation(type, interfaceType);
			AttachLocalizablePropertiesImplementations(type, interfaceType);
		}

		private void AttachNonLocalizableImplementation(IDynamicType type, Type interfaceType)
		{
			foreach (var p in Mapping.GetNonLocalizableProperties(interfaceType))
				type.DefineAutomaticProperty(p.PropertyName, p.PropertyType);
		}

		protected void AttachLocalizablePropertiesImplementations(IDynamicType type, Type interfaceType)
		{
			foreach (var p in Mapping.GetLocalizableProperties(interfaceType))
				AttachLocalizableProperty(type, p.PropertyName);
		}

		protected abstract void AttachLocalizableProperty(IDynamicType type, string name);

		protected override IDynamicType DefineType(string typeName, TypeAttributes attributes,
			Type baseType, Type[] interfaces)
		{
			var implementedInterfaces = GetImplementedInterfaces(interfaces);
			return base.DefineType(typeName, attributes, baseType, implementedInterfaces.ToArray());
		}

		protected IPropertyMapping FindIdProperty(IEnumerable<Type> interfaces)
		{
			return interfaces.Select(i => Mapping.GetPrimaryKey(i))
				.FirstOrDefault();
		}

		protected virtual IEnumerable<Type> GetImplementedInterfaces(Type[] interfaces)
		{
			var id = FindIdProperty(interfaces);
			if (id == null)
				throw ExecutionHelper.CreateException<TypeGenerationException>(
					"Could not find a proper 'Id' property. You should implement 'IEntity<TId>'.");

			return CreateBaseEntityInterfaces(interfaces).Concat(new[] 
			{
				TypeOfIEntityGeneric.MakeGenericType(id.PropertyType)
			});
		}

		protected virtual IEnumerable<Type> CreateBaseEntityInterfaces(IEnumerable<Type> interfaces)
		{
			return interfaces.Where(i => TypeOfIEntity.IsAssignableFrom(i))
				.Select(i => BaseInterfaceType.MakeGenericType(i));
		}

		protected abstract Type BaseInterfaceType
		{
			get;
		}
	}
}